SpringBoot Jar包启动原理

在执行java -jar命令时会在Jar包中找到META-INF/MANIFEST.MF文件,在该文件中通过Main-Class指定了应用的启动类,在SpringBoot的Jar包中Main-Class指定的是JarLauncher而非是正在的启动类,因为在Java中没有提供任何标准方式加载jar文件中的jar文件,故SpringBoot通过JarLauncher来执行启动类,同时加载jar包中依赖的jar文件。

1
2
3
4
5
6
7
8
9
10
11
12
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: eleven-springboot
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.icode.eleven.elevenspringboot.ElevenSpringbootApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.5
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

Spring-Boot-Classes指定的BOOT-INF/classes/中是应用程序类,Spring-Boot-Lib指定的BOOT-INF/lib/是第三方依赖jar路径。在Jar包中的org/springframework/boot/loader是SpringBoot的启动程序,JarLauncher就在该目录下。JarLauncher可以加载内部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的应用class

1
2
3
4
5
6
7
8
9
10
11
public class JarLauncher extends ExecutableArchiveLauncher {
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
if (entry.isDirectory()) {
return entry.getName().equals("BOOT-INF/classes/");
}
return entry.getName().startsWith("BOOT-INF/lib/");
};
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}

实例化JarLauncher时会先调用父类ExecutableArchiveLauncher的构造方法,最终调用超类Launcher中的createArchive()创建一个归档文件Archive。通过获取当前执行类所在的的磁盘路径,然后通过该路径打开一个文件,判断文件是否为目录来决定创建ExplodedArchive还是JarFileArchive。若是Jar文件这里的classPathIndexnull

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class ExecutableArchiveLauncher extends Launcher {
private static final String START_CLASS_ATTRIBUTE = "Start-Class";
private final Archive archive;
private final ClassPathIndexFile classPathIndex;
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
this.classPathIndex = getClassPathIndex(this.archive);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
public abstract class Launcher {
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
}
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
}

Archive有处理文件目录资源的ExplodedArchive和处理Jar包资源的JarFileArchive两个实现类。对Jar包的封装每个JarFileArchive都会对应一个JarFileJarFile被构造时会解析内部结构去获取jar包里的各个文件或文件夹,这些文件或文件夹会被封装到Entry中,也存储在JarFileArchive中。若Entry是个jar会解析成JarFileArchive。

1
2
3
4
5
6
7
public interface Archive extends Iterable<Archive.Entry>, AutoCloseable {
URL getUrl() throws MalformedURLException; // 获取该归档的url
// 获取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MF
Manifest getManifest() throws IOException;
// 获取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jar
List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
}

执行launch方法最终调用超类Launcher中的launch方法。createClassLoader方法会遍历出满足条件的jar包,并通过其创建一个LaunchedURLClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public abstract class Launcher {
protected void launch(String[] args) throws Exception {
if (!isExploded()) {
JarFile.registerUrlProtocolHandler();
}
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
launch(args, launchClass, classLoader);
}
}
public abstract class ExecutableArchiveLauncher extends Launcher {
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(guessClassPathSize());
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
}
if (this.classPathIndex != null) {
urls.addAll(this.classPathIndex.getUrls());
}
return createClassLoader(urls.toArray(new URL[0]));
}
}
public abstract class Launcher {
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}
}

会将BOOT-INF/lib/目录下的所有文件过滤出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public abstract class ExecutableArchiveLauncher extends Launcher {
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
Archive.EntryFilter searchFilter = this::isSearchCandidate;
Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter, (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry));
if (isPostProcessingClassPathArchives()) {
archives = applyClassPathArchivePostProcessing(archives);
}
return archives;
}
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(guessClassPathSize());
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
}
if (this.classPathIndex != null) {
urls.addAll(this.classPathIndex.getUrls());
}
return createClassLoader(urls.toArray(new URL[0]));
}
}
public class JarLauncher extends ExecutableArchiveLauncher {
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
if (entry.isDirectory()) {
return entry.getName().equals("BOOT-INF/classes/");
}
return entry.getName().startsWith("BOOT-INF/lib/");
};
protected boolean isSearchCandidate(Archive.Entry entry) {
return entry.getName().startsWith("BOOT-INF/");
}
protected boolean isNestedArchive(Archive.Entry entry) {
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}
protected boolean isPostProcessingClassPathArchives() {
return false;
}
}

创建一个NestedArchiveIterator迭代器,迭代时调用超类AbstractIterator的next方法会回调adapt方法,最终调用getNestedArchive对于comment为UNPACK:开头的文件,会进行解压。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class JarFileArchive implements Archive {
private static final String UNPACK_MARKER = "UNPACK:";
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException {
return new NestedArchiveIterator(this.jarFile.iterator(), searchFilter, includeFilter);
}
protected Archive getNestedArchive(Entry entry) throws IOException {
JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
if (jarEntry.getComment().startsWith(UNPACK_MARKER)) {
return getUnpackedNestedArchive(jarEntry);
}
try {
JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
return new JarFileArchive(jarFile);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to get nested archive for entry " + entry.getName(), ex);
}
}
private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException {
String name = jarEntry.getName();
if (name.lastIndexOf('/') != -1) {
name = name.substring(name.lastIndexOf('/') + 1);
}
Path path = getTempUnpackDirectory().resolve(name);
if (!Files.exists(path) || Files.size(path) != jarEntry.getSize()) {
unpack(jarEntry, path);
}
return new JarFileArchive(path.toFile(), path.toUri().toURL());
}
}
private class NestedArchiveIterator extends AbstractIterator<Archive> {
NestedArchiveIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
super(iterator, searchFilter, includeFilter);
}
@Override
protected Archive adapt(Entry entry) {
try {
return getNestedArchive(entry);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
private abstract static class AbstractIterator<T> implements Iterator<T> {
public T next() {
T result = adapt(this.current);
this.current = poll();
return result;
}
}

然后调用ExecutableArchiveLaunchergetMainClass或者正在的启动类,即META-INF/MANIFEST.MF文件中配置的Start-Class。Manifest就是用来接收解析META-INF/MANIFEST.MF出来的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class ExecutableArchiveLauncher extends Launcher {
private static final String START_CLASS_ATTRIBUTE = "Start-Class";
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
}

获取到启动类后通过反射调用启动类的main方法,即最终调用ElevenSpringbootApplication的main方法,从而去完成SpringBoot的启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class Launcher {
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(launchClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
}
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
public void run() throws Exception {
Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.setAccessible(true);
mainMethod.invoke(null, new Object[] { this.args });
}
}